package com.xvyy.tingshu.payment.service.impl;

import com.xvyy.tingshu.payment.util.PayUtil;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;

import com.wechat.pay.java.service.payments.jsapi.model.Payer;

import java.text.SimpleDateFormat;
import java.util.Date;

import com.xvyy.tingshu.account.client.UserAccountFeignClient;
import com.xvyy.tingshu.common.constant.SystemConstant;
import com.xvyy.tingshu.common.execption.TsException;
import com.xvyy.tingshu.common.rabbit.constant.MqConst;
import com.xvyy.tingshu.common.rabbit.service.RabbitService;
import com.xvyy.tingshu.common.result.Result;
import com.xvyy.tingshu.common.util.AuthContextHolder;
import com.xvyy.tingshu.model.account.RechargeInfo;
import com.xvyy.tingshu.model.order.OrderInfo;
import com.xvyy.tingshu.model.payment.PaymentInfo;
import com.xvyy.tingshu.order.client.OrderInfoFeignClient;
import com.xvyy.tingshu.payment.config.WxPayV3Config;
import com.xvyy.tingshu.payment.mapper.PaymentInfoMapper;
import com.xvyy.tingshu.payment.service.PaymentInfoService;
import com.xvyy.tingshu.payment.service.WxPayService;

import com.xvyy.tingshu.user.client.UserInfoFeignClient;
import com.xvyy.tingshu.vo.user.UserInfoVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;

import com.wechat.pay.java.core.exception.ServiceException;

import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.*;

import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService {

    @Autowired
    private UserInfoFeignClient userInfoFeignClient;

    @Autowired
    WxPayV3Config wxPayV3Config;
    @Autowired
    private PaymentInfoMapper paymentInfoMapper;
    @Autowired
    private OrderInfoFeignClient orderInfoFeignClient;
    @Autowired
    private UserAccountFeignClient userAccountFeignClient;
    @Autowired
    private RabbitService rabbitService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private PaymentInfoService paymentInfoService;


    @Override
    public Map<String, Object> createJsapi(Long paymentType, String orderNo) {
        // 0. 获取当前登录用户id
        Long userId = AuthContextHolder.getUserId();
        Map<String, Object> map = new HashMap<>();
        // 1. 插入支付流水信息 payment_info
        savePaymentInfo(orderNo, userId, paymentType);
        // 2. 返回前端未来发起微信小程序支付的必要参数（prepay_id）
        // 2.1 创建JsapiServiceExtension扩展对象
        JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(wxPayV3Config.getConfig()).build();
        // 2.2 创建准备发起预支付下单的请求对象
        PrepayRequest request = new PrepayRequest();
        // 2.3 给request赋值
        Amount amount = new Amount();
        amount.setTotal(1);// 测试时候不管买任何价格的商品 都只会话费1分钱
        request.setAmount(amount); // 金额
        request.setDescription("测试买了商品"); // 买的东西备注
        request.setAppid(wxPayV3Config.getAppid());  // 小程序id
        request.setMchid(wxPayV3Config.getMerchantId()); // 商户id
        request.setNotifyUrl(wxPayV3Config.getNotifyUrl()); // 异步回调地址
        request.setOutTradeNo(orderNo); // 给微信的一个订单编号（方便未来从微信那边查询该订单的支付状态）
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");  // 2018-06-08T10:34:56+08:00
        String format = simpleDateFormat.format(System.currentTimeMillis() + 1000 * 60 * 29);
        request.setTimeExpire(format); // 交易结束时间---(支付二维码的有效期是多久：29min之后过期)

        Payer payer = new Payer();
        // 3. 获取当前登录用户信息
        Result<UserInfoVo> userInfoVoResult = userInfoFeignClient.getUserInfoVo(AuthContextHolder.getUserId());
        UserInfoVo userInfoVoData = userInfoVoResult.getData();
        Assert.notNull(userInfoVoData, "远程调用user微服务，获取用户信息失败");
        payer.setOpenid(userInfoVoData.getWxOpenId());// 付款者的openId传入进去
        request.setPayer(payer);
        // 2.4 发起支付下单请求
        PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request);
        String timeStamp = response.getTimeStamp();
        String paySign = response.getPaySign();
        String signType = response.getSignType();
        String packageVal = response.getPackageVal();  // prepare_id
        String nonceStr = response.getNonceStr();
        // 3. 返回对象
        map.put("timeStamp", timeStamp);
        map.put("paySign", paySign);
        map.put("signType", signType);
        map.put("package", packageVal);
        map.put("nonceStr", nonceStr);
        return map;
    }

    @Override
    public Transaction queryPayStatus(String orderNo) {
        // 1.创建查询订单对象
        QueryOrderByOutTradeNoRequest outTradeNoRequest = new QueryOrderByOutTradeNoRequest();
        outTradeNoRequest.setMchid(wxPayV3Config.getMerchantId()); // 商户id
        outTradeNoRequest.setOutTradeNo(orderNo);
        // 2.查询
        try {
            JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(wxPayV3Config.getConfig()).build();
            return service.queryOrderByOutTradeNo(outTradeNoRequest);
        } catch (ServiceException e) {
            // API返回失败, 例如ORDER_NOT_EXISTS
            System.out.printf("code=[%s], message=[%s]\n", e.getErrorCode(), e.getErrorMessage());
            System.out.printf("response body=[%s]\n", e.getResponseBody());
            throw new TsException(201, "查询订单支付状态失败");
        }
    }

    @Override
    public void wxPaySuccess(String orderNo) {
        // 1. payment_info 修改支付状态为未支付
        int count = paymentInfoMapper.updatePaymentStatus(orderNo, SystemConstant.PAYMENT_STATUS_UNPAID);
        if (count == 0) {
            log.info("修改支付状态失败");
            return;
        }
        // 2. 查询支付表
        PaymentInfo paymentInfo = paymentInfoMapper.selectOne(new LambdaQueryWrapper<PaymentInfo>().eq(PaymentInfo::getOrderNo, orderNo));
        if (paymentInfo == null) {
            return;
        }
        // 3. 获取支付类型
        String paymentType = paymentInfo.getPaymentType();
        // 4. 支付成功回调的业务逻辑 应该放到MQ中去执行
        String routing = paymentType.equals("1301") ? MqConst.ROUTING_ORDER_PAY_SUCCESS : MqConst.ROUTING_RECHARGE_PAY_SUCCESS;
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("pay:success:callback:msg:retry", "1", 40, TimeUnit.SECONDS);
        if (aBoolean) {
            rabbitService.sendMessage(MqConst.EXCHANGE_ORDER, routing, orderNo);
        }
    }

    @Override
    public Transaction notifier(HttpServletRequest httpServletRequest) {
        String wechatSignature = httpServletRequest.getHeader("Wechatpay-Signature");
        String wechatPaySerial = httpServletRequest.getHeader("Wechatpay-Serial");
        String wechatpayNonce = httpServletRequest.getHeader("Wechatpay-Nonce");
        String wechatTimestamp = httpServletRequest.getHeader("Wechatpay-Timestamp");
        String bodyOldStr = PayUtil.readData(httpServletRequest);
        // 1.构造 RequestParam
        RequestParam requestParam = new RequestParam.Builder().serialNumber(wechatPaySerial).nonce(wechatpayNonce).signature(wechatSignature).timestamp(wechatTimestamp).body(bodyOldStr).build();
        // 2.初始化 NotificationParser
        NotificationParser parser = new NotificationParser(wxPayV3Config.getConfig());
        // 3.解析（验证签名 解析数据 微信的公钥）
        Transaction transaction = null;
        try {
            transaction = parser.parse(requestParam, Transaction.class);
            return transaction;
        } catch (Exception e) {
            log.error("验签和解析数据失败:{}", e.getMessage());
            return null;
        }
    }


    @Override
    public Map<String, Object> createNative(String paymentType, String orderNo, Long userId) {
        try {
            //保存支付记录
            PaymentInfo paymentInfo = savePaymentInfo(orderNo, userId, Long.valueOf(paymentType));
            // 构建service
            NativePayService service = new NativePayService.Builder().config(wxPayV3Config.getConfig()).build();
            // request.setXxx(val)设置所需参数，具体参数可见Request定义
            // request.setXxx(val)设置所需参数，具体参数可见Request定义
            com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest request = new com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest();
            com.wechat.pay.java.service.payments.nativepay.model.Amount amount = new com.wechat.pay.java.service.payments.nativepay.model.Amount();
            amount.setTotal(1);
            request.setAmount(amount);
            request.setAppid(wxPayV3Config.getAppid());
            request.setMchid(wxPayV3Config.getMerchantId());
            request.setDescription(paymentInfo.getContent());
            request.setNotifyUrl(wxPayV3Config.getNotifyUrl());
            request.setOutTradeNo(paymentInfo.getOrderNo());
            request.setDescription("商品描述");
            // 调用下单方法，得到应答
            com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse response = service.prepay(request);
            // 使用微信扫描 code_url 对应的二维码，即可体验Native支付
            System.out.println(response.getCodeUrl());
            Map result = new HashMap<>();
            result.put("codeUrl", response.getCodeUrl());
            return result;
        } catch (ServiceException e) {
            e.printStackTrace();
            throw new TsException( 201, e.getErrorMessage());
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
            throw new TsException( 201, "订单号不存在");
        } catch (Exception e) {
            e.printStackTrace();
            throw new TsException( 201, "微信下单异常");
        }
    }

    /**
     * 保存支付流水信息
     *
     * @param orderNo
     * @param userId
     * @param paymentType
     */
    private PaymentInfo savePaymentInfo(String orderNo, Long userId, Long paymentType) {
// 1.根据订单编号查询该订单的支付流水是否存在
        LambdaQueryWrapper<PaymentInfo> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(PaymentInfo::getOrderNo, orderNo);
        wrapper.eq(PaymentInfo::getUserId, userId);
        PaymentInfo paymentInfo = paymentInfoMapper.selectOne(wrapper);
        if (paymentInfo == null) {
            paymentInfo = new PaymentInfo();
            paymentInfo.setUserId(userId);
            paymentInfo.setPaymentType(String.valueOf(paymentType)); // 支付类型
            paymentInfo.setOrderNo(orderNo);
            paymentInfo.setPayWay("1101");  // 支付方式
            paymentInfo.setOutTradeNo(orderNo);
            if (paymentType.toString().equals("1301")) {
                // 订单支付类型
                // 1. 根据订单编号查询订单业务信息
                Result<OrderInfo> orderInfoResult = orderInfoFeignClient.getOrderInfo(orderNo);
                OrderInfo orderInfoData = orderInfoResult.getData();
                Assert.notNull(orderInfoData, "该订单不存在");
                // 2. 根据订单信息赋值
                paymentInfo.setAmount(orderInfoData.getOrderAmount());  // 根据支付类型不同赋值不同的值
                paymentInfo.setContent(orderInfoData.getOrderTitle());
            } else {
                // 充值支付类型
                // 1. 根据订单编号查询充值业务信息
                Result<RechargeInfo> rechargeInfoResult = userAccountFeignClient.getRechargeInfoByOrder(orderNo);
                RechargeInfo rechargeInfoData = rechargeInfoResult.getData();
                Assert.notNull(rechargeInfoData, "该充值订单不存在");
                // 2. 根据充值信息赋值
                paymentInfo.setAmount(rechargeInfoData.getRechargeAmount());  // 根据支付类型不同赋值不同的值
                paymentInfo.setContent("充钱");
            }
            // 3. 其他信息赋值
            paymentInfo.setPaymentStatus(SystemConstant.PAYMENT_STATUS_UNPAID);
            paymentInfo.setCallbackTime(new Date());   // 回调时间
            paymentInfo.setCallbackContent(""); // 回调内容
            // 4. 流水插入数据库
            int insert = paymentInfoMapper.insert(paymentInfo);
            log.info("插入支付流水信息，影响的行数：{}", insert);
        }
        return paymentInfo;
    }

}
